/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.inspur.edp.metadata.rtcustomization.servermanager.repository;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inspur.edp.lcm.metadata.api.IMetadataContent;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.lcm.metadata.api.entity.Metadata4Ref;
import com.inspur.edp.lcm.metadata.api.entity.MetadataHeader;
import com.inspur.edp.lcm.metadata.api.entity.MetadataPackageHeader;
import com.inspur.edp.lcm.metadata.api.entity.MetadataProperties;
import com.inspur.edp.lcm.metadata.api.entity.MetadataReference;
import com.inspur.edp.lcm.metadata.api.entity.ServiceUnitInfo;
import com.inspur.edp.lcm.metadata.common.MetadataPropertyUtils;
import com.inspur.edp.lcm.metadata.common.ServiceUtils;
import com.inspur.edp.lcm.metadata.common.configuration.MetadataSerializerHelper;
import com.inspur.edp.lcm.metadata.spi.MetadataContentSerializer;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdCustomContent;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdRefs;
import com.inspur.edp.metadata.rtcustomization.api.exception.LcmConfigResolveException;
import com.inspur.edp.metadata.rtcustomization.api.exception.ErrorCodes;
import com.inspur.edp.metadata.rtcustomization.api.exception.LcmMetadataException;
import com.inspur.edp.metadata.rtcustomization.api.exception.LcmParseException;
import com.inspur.edp.metadata.rtcustomization.servermanager.CustomizationServiceContext;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.CustomizationDataProducer;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.CustomizationMetadataRefsRepository;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.CustomizationMetadataRepository;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.GeneratedRepo;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

import com.inspur.edp.metadata.rtcustomization.servermanager.utils.MetadataStorageUtils;
import io.iec.edp.caf.boot.context.CAFContext;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

@Slf4j
public class GeneratedRepoService {
    private GeneratedRepo generatedRepo;
    private CustomizationMetadataRepository metadataRepository;
    private CustomizationMetadataRefsRepository metadataRefsRepository;


    public GeneratedRepoService(CustomizationMetadataRepository metadataRepository, CustomizationMetadataRefsRepository metadataRefsRepository, CustomizationDataProducer cdp) {
        this.generatedRepo = new GeneratedRepo(cdp);
        this.metadataRepository = metadataRepository;
        this.metadataRefsRepository = metadataRefsRepository;
    }

    /**
     * 根据元素局id获取运行时生成的元数据
     * 未使用，废弃。使用getMetadata方法即可
     *
     * @param id 元数据id
     * @return 元数据
     */
    @Deprecated
    public GspMetadata getGeneratedMetadataListById(String id) {
        var customizedMetadataList = generatedRepo.getGeneratedListById(id);
        if (customizedMetadataList == null || customizedMetadataList.size() == 0) {
            return null;
        }
        var metadata = buildGspMetadata(customizedMetadataList.get(0), false);

        return metadata;
    }

    public List<Metadata4Ref> getAllRTGeneratedMetadata(String metadataTypes) {
        String[] types = metadataTypes.split(",");
        List<String> typeList = new ArrayList<>(Arrays.asList(types));
        return getAllRTGeneratedMetadata(typeList, false);
    }

    public List<Metadata4Ref> getAllRTGeneratedMetadata(List<String> metadataTypes, boolean isNoPackageInfo) {
        List<GspMdCustomContent> customizedMetadataList = generatedRepo.getGeneratedList();
        if (customizedMetadataList == null || customizedMetadataList.size() == 0) {
            return null;
        }
        List<Metadata4Ref> metadataList = new ArrayList<>();
        customizedMetadataList.forEach(item -> {
            if(!CollectionUtils.isEmpty(metadataTypes)){
                List<String> md = metadataTypes.stream().filter(type -> type.toLowerCase().equals(item.getType().toLowerCase())).collect(Collectors.toList());
                if (md != null && md.size() > 0) {
                    if(isNoPackageInfo){
                        var metadata = buildMetadata4Ref(item,null);
                        metadataList.add(metadata);
                    }else{
                        var metadata = buildMetadata4Ref(item);
                        metadataList.add(metadata);
                    }
                }
            }else {
                if(isNoPackageInfo){
                    var metadata = buildMetadata4Ref(item,null);
                    metadataList.add(metadata);
                }else{
                    var metadata = buildMetadata4Ref(item);
                    metadataList.add(metadata);
                }
            }
        });

        return metadataList;
    }

    /**
     * 根据条件获取运行时生成的元数据列表
     * 分页查询，用在设计时元数据选择器。文件化多租户下用不到，暂不修改。
     *
     * @param metadataTypeList
     * @param keyword
     * @param beginNum
     * @param size
     * @param isNoPackageInfo
     * @return
     */
    public List<Metadata4Ref> getRTGeneratedMetadataListByCondition(List<String> metadataTypeList, String keyword, String bizobjectID, int beginNum, int size, boolean isNoPackageInfo) {
        List<GspMdCustomContent> customizedMetadataList = generatedRepo.getGeneratedListByCondition(keyword, bizobjectID, metadataTypeList, beginNum, size);
        if (CollectionUtils.isEmpty(customizedMetadataList)) {
            return null;
        }
        List<Metadata4Ref> metadataList = new ArrayList<>();
        customizedMetadataList.forEach(item -> {
            var metadata = isNoPackageInfo ? buildMetadata4Ref(item, null) : buildMetadata4Ref(item);
            metadataList.add(metadata);
        });

        return metadataList;
    }

    /**
     * 根据条件获取运行时生成的元数据数量
     * 分页查询，用在设计时元数据选择器。文件化多租户下用不到，暂不修改。
     *
     * @param metadataTypeList
     * @param keyword
     * @return
     */
    public int getRTGeneratedMetadataCountByCondition(List<String> metadataTypeList, String keyword, String bizobjectID) {
        return Math.toIntExact(generatedRepo.getGeneratedListCountByCondition(keyword, bizobjectID, metadataTypeList));
    }

    /**
     * 根据ID获取运行时生成的元数据
     * @param metadataId
     * @param isNoPackageInfo
     * @return
     */
    public Metadata4Ref getRTGeneratedMetadataById(String metadataId, boolean isNoPackageInfo) {
        // id是主键，只有一个
        GspMdCustomContent gspMdCustomContent = generatedRepo.getGeneratedMetadataById(metadataId);
        if (gspMdCustomContent == null) {
            return null;
        }

        Metadata4Ref metadata;
        if (isNoPackageInfo) {
            metadata = buildMetadata4Ref(gspMdCustomContent, null);
        } else {
            metadata = buildMetadata4Ref(gspMdCustomContent);
        }
        return metadata;
    }

    public List<Metadata4Ref> getAllRTGeneratedMetadata() {
        List<GspMdCustomContent> customizedMetadataList = generatedRepo.getGeneratedList();
        if (customizedMetadataList == null || customizedMetadataList.size() == 0) {
            return null;
        }
        List<Metadata4Ref> metadataList = new ArrayList<>();
        customizedMetadataList.forEach(item -> {
            var metadata = buildMetadata4Ref(item);
            metadataList.add(metadata);
        });

        return metadataList;
    }

    public GspMetadata getMetadata(String metadataId) {
        GspMdCustomContent metadata4Database = findByMetadataId(metadataId);
        if (metadata4Database == null) {
            return null;
        }
        GspMetadata metadata = buildGspMetadata(metadata4Database, true);
        List<GspMdRefs> refsList = metadataRefsRepository.findAllByMdId(metadataId);
        if (refsList == null || refsList.size() <= 0) {
            return metadata;
        }
        List<MetadataReference> mdRefs = buildMetadataReference(refsList);
        metadata.setRefs(mdRefs);
        return metadata;
    }

    public GspMdCustomContent findByMetadataId(String metadataId) {
        GspMdCustomContent gspMdCustomContent = generatedRepo.getGeneratedMetadataById(metadataId);
        if (gspMdCustomContent != null && gspMdCustomContent.getLastChangedOn() == null) {
            gspMdCustomContent.setLastChangedOn(LocalDateTime.now());
        }
        return gspMdCustomContent;
    }

    public String buildMetadataString(GspMdCustomContent md) {
        if (md == null) {
            return null;
        }
        MetadataHeader header = new MetadataHeader(md.getId(), md.getNameSpace(), md.getCode(), md.getName(), md.getType(), md.getBizObjId());
        List<GspMdRefs> refsList = metadataRefsRepository.findAllByMdId(md.getId());
        List<MetadataReference> mdRefs = CollectionUtils.isEmpty(refsList) ? new ArrayList<>() : buildMetadataReference(refsList);

        GspMetadata metadata = new GspMetadata(header, mdRefs, md.getExtendProperty());

        ObjectMapper mapper = ServiceUtils.getMapper();
        String metadataStr;
        try {
            metadataStr = mapper.writeValueAsString(metadata);
            JsonNode metadataObj = mapper.readTree(metadataStr);
            ObjectNode objNode = (ObjectNode) metadataObj;
            objNode.set(MetadataPropertyUtils.content, mapper.readTree(md.getContent()));
            metadataStr = mapper.writeValueAsString(objNode);
        } catch (JsonProcessingException e) {
            throw new LcmParseException(e, ErrorCodes.ECP_PARSE_0011, md.getNameSpace(), md.getId(), md.getCode(), md.getType());
        }
        return metadataStr;
    }

    public void deleteRefs(String mdId) {
        metadataRefsRepository.deleteByMdId(mdId);
    }

    public void deleteGeneratedMetadata(String mdId) {
        GspMdCustomContent metadata = findByMetadataId(mdId);
        if (metadata != null) {
            metadataRepository.deleteById(mdId);
        }
    }

    public void saveMetadata(GspMetadata gspMetadata) {
        //组织元数据内容
        GspMdCustomContent gspMdCustomContent = buildGspMdCustomContent(gspMetadata);
        metadataRepository.save(gspMdCustomContent);
        //组织依赖关系
        List<GspMdRefs> metadataRefs = buildMetadataRefs(gspMetadata);
        if (metadataRefs != null && metadataRefs.size() > 0) {
            metadataRefs.forEach(item -> {
                metadataRefsRepository.save(item);
            });
        }
    }


    public List<GspMdRefs> getMdRefsByRefMdId(String refMdId) {
        return metadataRefsRepository.findAllByRefMdId(refMdId);
    }

    /**
     * 根据元数据空间、元数据编码、元数据类型获取元数据
     * 用在校验元数据是否存在
     * 文件化，无租户，新增元数据的校验会有问题，因为会校验出和其他租户的冲突，没有这种场景。
     *
     * @param nameSpace 命名空间
     * @param code 编号
     * @param type 类型
     * @return 元数据列表
     */
    public List<GspMdCustomContent> getAllByNameSpaceAndCodeAndType(String nameSpace, String code, String type) {
        List<GspMdCustomContent> allMdList;
        // 如果是文件化，并且上下文中有租户信息，过滤掉其他租户的元数据
        if (MetadataStorageUtils.isUsingSecondDatasource() && CAFContext.current.getTenantId() != -1) {
            allMdList = metadataRepository.findAllByCodeAndTypeAndTenantId(code, type, CAFContext.current.getTenantId());
        } else {
            // 非文件化，或者上下文中没有租户信息（没有租户信息，说明没有用户登录，获取的元数据和租户无关），即CAFContext.current.getTenantId() == -1，返回所有元数据
            allMdList = metadataRepository.findAllByCodeAndType(code, type);
        }
        if (allMdList == null || allMdList.size() == 0) {
            return null;
        }
        List<GspMdCustomContent> metadataList = new ArrayList<>();

        allMdList.forEach(item -> {
            boolean nameSpaceEqual = (!StringUtils.isEmpty(nameSpace) && nameSpace.equals(item.getNameSpace()))
                    || (StringUtils.isEmpty(nameSpace) && StringUtils.isEmpty(item.getNameSpace()));
            if (nameSpaceEqual && code.equals(item.getCode()) && type.equals(item.getType())) {
                metadataList.add(item);
            }
        });

        return metadataList;
    }

    private Metadata4Ref buildMetadata4Ref(GspMdCustomContent metadata4Database) {
        return buildMetadata4Ref(metadata4Database, new MetadataPackageHeader());
    }

    private Metadata4Ref buildMetadata4Ref(GspMdCustomContent metadata4Database, MetadataPackageHeader metadataPackageHeader) {
        Metadata4Ref metadata4Ref = new Metadata4Ref();
        metadata4Ref.setMetadata(new GspMetadata());
        metadata4Ref.getMetadata().setHeader(new MetadataHeader());
        if(Objects.nonNull(metadataPackageHeader)){
            metadata4Ref.setPackageHeader(metadataPackageHeader);
        }
        metadata4Ref.setServiceUnitInfo(new ServiceUnitInfo());
        metadata4Ref.getMetadata().getHeader().setId(metadata4Database.getId());
        metadata4Ref.getMetadata().getHeader().setName(metadata4Database.getName());
        metadata4Ref.getMetadata().getHeader().setCode(metadata4Database.getCode());
        metadata4Ref.getMetadata().getHeader().setType(metadata4Database.getType());
        metadata4Ref.getMetadata().getHeader().setBizobjectID(metadata4Database.getBizObjId());
        metadata4Ref.getMetadata().getHeader().setNameSpace(metadata4Database.getNameSpace());
        metadata4Ref.getMetadata().setExtendProperty(metadata4Database.getExtendProperty());
        return metadata4Ref;
    }

    private GspMetadata buildGspMetadata(GspMdCustomContent gspMdCustomContent, boolean flag) {
        MetadataHeader metadataHeader = new MetadataHeader(gspMdCustomContent.getId(), gspMdCustomContent.getNameSpace(), gspMdCustomContent.getCode(), gspMdCustomContent.getName(), gspMdCustomContent.getType(), gspMdCustomContent.getBizObjId());
        IMetadataContent content = null;
        if (flag) {
            MetadataContentSerializer manager = MetadataSerializerHelper.getInstance().getManager(gspMdCustomContent.getType());
            if (manager == null) {
                throw new LcmConfigResolveException(ErrorCodes.ECP_CONFIG_RESOLVE_0006, gspMdCustomContent.getType());
            }
            ObjectMapper objectMapper = ServiceUtils.getMapper();
            JsonNode metadataObj;
            try {
                metadataObj = objectMapper.readTree(gspMdCustomContent.getContent());
            } catch (Exception e) {
                log.error(gspMdCustomContent.getContent());
                throw new LcmParseException(e, ErrorCodes.ECP_PARSE_0004);
            }
            content = manager.DeSerialize(metadataObj);
            if (content == null) {
                throw new LcmParseException(ErrorCodes.ECP_PARSE_0003, gspMdCustomContent.getId());
            }
        }
        MetadataProperties properties = new MetadataProperties("", gspMdCustomContent.getLastChangedOn().toString());
        return new GspMetadata(metadataHeader, content, gspMdCustomContent.getExtendProperty(), properties);
    }

    //region private method
    private List<GspMdRefs> buildMetadataRefs(GspMetadata metadata) {
        if (Objects.isNull(metadata)) {
            return null;
        }
        if (metadata.getRefs() == null || metadata.getRefs().size() <= 0) {
            return null;
        }
        List<GspMdRefs> metadataRefsList = new ArrayList<>();
        metadata.getRefs().forEach(item -> {
            //根据mdid及refmdid判断是否存在数据，存在则返回，不存在则插入
            if (Objects.isNull(item.getMetadata())) {
                item.setMetadata(metadata.getHeader());
            }
            List<GspMdRefs> refs = metadataRefsRepository.findAllByMdIdAndRefMdId(item.getMetadata().getId(), item.getDependentMetadata().getId());
            if (refs != null) {
                return;
            }
            GspMdRefs metadataRefs = new GspMdRefs();
            metadataRefs.setId(UUID.randomUUID().toString());
            metadataRefs.setMdId(item.getMetadata().getId());
            metadataRefs.setMdNameSpace(item.getMetadata().getNameSpace());
            metadataRefs.setMdCode(item.getMetadata().getCode());
            metadataRefs.setMdType(item.getMetadata().getType());
            metadataRefs.setRefMdId(item.getDependentMetadata().getId());
            metadataRefs.setRefMdNameSpace(item.getDependentMetadata().getNameSpace());
            metadataRefs.setRefMdCode(item.getDependentMetadata().getCode());
            metadataRefs.setRefMdType(item.getDependentMetadata().getType());
            metadataRefsList.add(metadataRefs);
        });

        return metadataRefsList;
    }

    private List<MetadataReference> buildMetadataReference(List<GspMdRefs> refs) {
        List<MetadataReference> refList = new ArrayList<>();
        refs.forEach(item -> {
            MetadataReference ref = new MetadataReference();
            ref.setMetadata(new MetadataHeader());
            ref.setDependentMetadata(new MetadataHeader());
            ref.setReferenceDetail(new ArrayList<>());
            ref.getMetadata().setId(item.getMdId());
            ref.getMetadata().setCode(item.getMdCode());
            ref.getMetadata().setNameSpace(item.getMdNameSpace());
            ref.getMetadata().setType(item.getMdType());
            ref.getDependentMetadata().setId(item.getRefMdId());
            ref.getDependentMetadata().setNameSpace(item.getRefMdNameSpace());
            ref.getDependentMetadata().setCode(item.getRefMdCode());
            ref.getDependentMetadata().setType(item.getRefMdType());
            refList.add(ref);
        });

        return refList;
    }

    /**
     * 构建GspMdCustomContent
     * 这个方法目前给保存元数据使用，里面有租户垂直越权校验，其他方法慎用
     *
     * @param metadata 元数据实体
     * @return 数据库实体
     */
    private GspMdCustomContent buildGspMdCustomContent(GspMetadata metadata) {
        String metadataId = metadata.getHeader().getId();
        // 是否更新
        GspMdCustomContent gspMdCustomContent = generatedRepo.getGeneratedMetadataById(metadataId);
        // 这个方法给保存使用，在文件化时需要增加越权校验
        if (gspMdCustomContent == null) {
            gspMdCustomContent = new GspMdCustomContent(metadataId, CustomizationServiceContext.getUserName());
            // 如果是文件化，并且上下文中有租户信息，保存的时候保存上tenantId
            if (MetadataStorageUtils.isUsingSecondDatasource() && CAFContext.current.getTenantId() != -1) {
                gspMdCustomContent.setTenantId(CAFContext.current.getTenantId());
            }
        } else {
            // 如果是文件化，并且上下文中有租户信息，则要进行租户越权校验
            if (MetadataStorageUtils.isUsingSecondDatasource() && CAFContext.current.getTenantId() != -1) {
                // 如果元数据的tenantId为null，说明是共享元数据，不允许保存。
                // 如果元数据的tenantId和上下文中的不一样，说明是别的租户的元数据，不允许保存。
                if (gspMdCustomContent.getTenantId() == null || gspMdCustomContent.getTenantId() != CAFContext.current.getTenantId()) {
                    throw new LcmMetadataException(ErrorCodes.ECP_METADATA_0023, gspMdCustomContent.getName());
                }
            }
        }

        MetadataContentSerializer manager = MetadataSerializerHelper.getInstance().getManager(metadata.getHeader().getType());
        if (manager == null) {
            throw new LcmConfigResolveException(ErrorCodes.ECP_CONFIG_RESOLVE_0006, metadata.getHeader().getType());
        }
        JsonNode jsonNode = manager.Serialize(metadata.getContent());
        ObjectMapper objectMapper = ServiceUtils.getMapper();
        String contentStr = null;
        try {
            contentStr = objectMapper.writeValueAsString(jsonNode);
        } catch (JsonProcessingException e) {
            log.error("反序列化元数据[{}]内容节点失败", metadataId, e);
        }
        gspMdCustomContent.setContent(contentStr);
        Character isExtend = gspMdCustomContent.getIsExtend() == null ? '0' : gspMdCustomContent.getIsExtend();
        gspMdCustomContent.update(metadata.getHeader().getCode(), metadata.getHeader().getName(), metadata.getHeader().getType(),
                metadata.getHeader().getBizobjectID(), contentStr, CustomizationServiceContext.getUserName(), LocalDateTime.now(),
                isExtend, metadata.getHeader().getNameSpace(), metadata.getExtendProperty());

        return gspMdCustomContent;
    }

    public List<GspMdCustomContent> findAllLastChangedOn() {
        return generatedRepo.findAllLastChangedOn();
    }

    public void updateLastChangedOnWhereIsNull() {
        generatedRepo.updateLastChangedOnWhereIsNull();
    }
}
